package cn.dapeng.spring.core;

import cn.dapeng.spring.aware.BeanFactoryAware;
import org.apache.commons.lang3.StringUtils;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

    protected Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    private Map<String, Object> singletonDefinitionMap = new ConcurrentHashMap<>(256);
    private Map<Class<?>, Set<String>> typeMap = new ConcurrentHashMap<>(256);

    /*
     * Spring 中属性依赖的情况：
     * 如果循环依赖中不存在单例，则不可以，否则可以
     */
    private ThreadLocal<Map<String, Object>> earlyExposeBuildingThreadLocal = new ThreadLocal<>();
    /**
     *
     */
    private ThreadLocal<Set<String>> circularDependencyThreadLocal = new ThreadLocal<>();

    private List<BeanPostProcessor> beanPostProcessors = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void registerBeanPostProcessor(BeanPostProcessor bpp) {
        this.beanPostProcessors.add(bpp);
        if (bpp instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bpp).setBeanFactory(this);
        }
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegistException {
        Objects.requireNonNull(beanName, "注册bean需要给入beanName");
        Objects.requireNonNull(beanDefinition, "注册bean需要给入beanDefinition");

        if (beanDefinition.validate()) {
            throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义不合法：" + beanDefinition);
        }

        if (this.containsBeanDefinition(beanName)) {
            throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));
        }

        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return this.beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return this.beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public Object getBean(String beanName) throws Throwable {
        return this.doGetBean(beanName);
    }

    @Override
    public <T> T getBean(Class<T> type) throws Throwable {
        Set<String> names = typeMap.get(type);

        if (!names.isEmpty()) {
            if (names.size() == 1) {
                return (T) this.getBean(names.iterator().next());
            } else {
                BeanDefinition bd;
                String primaryName = null;
                StringBuilder nameBuilder = new StringBuilder();
                for (String name : names) {
                    bd = this.getBeanDefinition(name);
                    if (bd != null && bd.isPrimary()) {
                        if (primaryName != null) {
                            String errMsg = type + "类型的Bean存储多个Primary[" + name + "]";
                            throw new RuntimeException(errMsg);
                        } else {
                            primaryName = name;
                        }
                    }
                    nameBuilder.append("" + name);
                }

                if (primaryName != null) {
                    return (T) this.getBean(primaryName);
                } else {
                    String errMsg = type + "类型存在多个[" + nameBuilder + "]，但无法确定Primary";
                    throw new RuntimeException(errMsg);
                }
            }
        }
        return null;
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws Throwable {
        Set<String> names = typeMap.get(type);
        if (names.isEmpty()) {
            return null;
        }
        Map<String, T> map = new HashMap<>();
        for (String name : names) {
            map.put(name, (T) this.getBean(name));
        }
        return map;
    }

    @Override
    public Class<?> getType(String beanName) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能为空");

        BeanDefinition bd = this.getBeanDefinition(beanName);
        Class<?> type = bd.getBeanClass();

        if (type != null) {
            if (StringUtils.isNotBlank(bd.getFactoryMethodName())) {
                type = type.getDeclaredMethod(bd.getFactoryMethodName()).getReturnType();
            }
        } else {
            type = this.getType(bd.getFactoryBeanName());
            type = type.getDeclaredMethod(bd.getFactoryMethodName()).getReturnType();
        }

        return type;
    }

    @Override
    public <T> List<T> getBeansOfTypeList(Class<T> type) throws Throwable {
        Set<String> names = this.typeMap.get(type);
        List<T> beans = null;
        if(names != null) {
            beans = new ArrayList<>();
            for(String name : names){
                beans.add((T) this.getBean(name));
            }
        }
        return beans;
    }

    protected Object doGetBean(String beanName) throws Throwable {
        Objects.requireNonNull(beanName, "beanName不能为空");

        Object instance = singletonDefinitionMap.get(beanName);
        if (instance != null) {
            return instance;
        }

        instance = this.getFromEarlyExposeBuildingBeans(beanName);
        if (instance != null) { //这是属性依赖时的循环引用，返回提前暴露的实例
            return instance;
        }

        BeanDefinition bd = this.getBeanDefinition(beanName);
        Objects.requireNonNull(bd, "beanDefinition不能为空");

        // 处理循环依赖问题
        Set circularDependencyBeans = circularDependencyThreadLocal.get();

        if (circularDependencyBeans == null) {
            circularDependencyBeans = new HashSet(16);
            circularDependencyThreadLocal.set(circularDependencyBeans);
        }
        if (circularDependencyBeans.contains(beanName)) {
            throw new CircularDependencyException(beanName);
        }

        circularDependencyBeans.add(beanName);

        if (bd.isSingleton()) {
            synchronized (singletonDefinitionMap) {
                instance = singletonDefinitionMap.get(beanName);
                if (instance == null) {
                    instance = doCreateInstance(beanName, bd);
                    singletonDefinitionMap.put(beanName, instance);
                }
            }
        } else {
            instance = doCreateInstance(beanName, bd);
        }

        // 创建好实例后，移除创建中记录
        circularDependencyBeans.remove(beanName);
        return instance;
    }

    private Object doCreateInstance(String beanName, BeanDefinition bd) throws Throwable {
        Class<?> type = bd.getBeanClass();
        Object instance;

        if (type != null) {
            if (StringUtils.isBlank(bd.getFactoryMethodName())) {
                instance = createInstanceByConstructor(bd);
            } else {
                instance = createInstanceByStaticFactoryMethod(bd);
            }
        } else {
            instance = createInstanceByFactoryBean(bd);
        }

        this.doEarlyExposeBuildingBeans(beanName, instance);

        // 给入属性依赖
        this.setPropertyDIValues(bd, instance);

        this.removeEarlyExposeBuildingBeans(beanName);

        // 应用bean初始化前的处理
        instance = this.applyPostProcessBeforeInitialization(instance, beanName);

        this.doInit(bd, instance);

        // 应用bean初始化后的处理
        instance = this.applyPostProcessAfterInitialization(instance, beanName);

        return instance;
    }

    /**
     * 构造函数实例化bean实例
     *
     * @param bd
     * @return
     */
    private Object createInstanceByConstructor(BeanDefinition bd) throws Throwable {
        /*
         * 构造参数依赖注入，这里需要做些什么？
         */

        /*1 得到真正的参数值，因为
         *List<?> constructorArgumentValues = bd.getConstructorArgumentValues();
         * constructorArgumentValues 中可能有 BeanReference
         */
        Object[] args = this.getConstructorArgumentValues(bd);
        //缓存构造参数，供后面需要这些参数地方使用
        bd.setConstructorArgumentRealValues(args);
        // 2 判定该调用哪个构造方法来创建实例
        return this.determineConstructor(bd, args).newInstance(args);
    }

    /**
     * 普通成员方法构造bean
     * 成员函数
     *
     * @param bd
     * @return
     * @throws Exception
     */
    private Object createInstanceByFactoryBean(BeanDefinition bd) throws Throwable {
        Object factoryBean = this.doGetBean(bd.getFactoryBeanName());
        Method m = factoryBean.getClass().getMethod(bd.getFactoryMethodName(), null);
        return m.invoke(factoryBean);
    }

    /**
     * 静态工厂方法构造bean
     * @param bd
     * @return
     * @throws Exception
     */
    private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {
        Class<?> type = bd.getBeanClass();
        Method m = type.getMethod(bd.getFactoryMethodName(), null);
        return m.invoke(null);
    }

    private void doInit(BeanDefinition bd, Object instance) throws Exception {
        if (StringUtils.isNotBlank(bd.getInitMethodName())) {
            Method method = instance.getClass().getMethod(bd.getInitMethodName());
            method.invoke(instance);
        }
    }

    @Override
    public void close() throws IOException {
        for (Map.Entry<String, BeanDefinition> e : this.beanDefinitionMap.entrySet()) {
            String beanName = e.getKey();
            BeanDefinition bd = e.getValue();

            if (bd.isSingleton() && StringUtils.isNotBlank(bd.getDestroyMethodName())) {
                Object instance = singletonDefinitionMap.get(beanName);
                try {
                    Method destroyMethod = instance.getClass().getMethod(bd.getDestroyMethodName());
                    destroyMethod.invoke(instance);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
                    throw new RuntimeException("执行bean[" + beanName + "]" + bd + "的销毁方法异常", exception);
                }
            }
        }
    }

    private Object getFromEarlyExposeBuildingBeans(String beanName) {
        Map<String, Object> earlyExposeBuildingBeansMap = earlyExposeBuildingThreadLocal.get();
        return earlyExposeBuildingBeansMap == null ? null : earlyExposeBuildingBeansMap.get(beanName);
    }

    private void doEarlyExposeBuildingBeans(String beanName, Object instance) {
        Map<String, Object> earlyExposeBuildingBeansMap = earlyExposeBuildingThreadLocal.get();
        if (earlyExposeBuildingBeansMap == null) {
            earlyExposeBuildingBeansMap = new HashMap<>(16);
            earlyExposeBuildingThreadLocal.set(earlyExposeBuildingBeansMap);
        }
        earlyExposeBuildingBeansMap.put(beanName, instance);
    }

    private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Throwable {
        if (bd.getPropertyValues() == null) {
            return;
        }

        for (PropertyValue property : bd.getPropertyValues()) {
            if (property.getValue() == null) {
                continue;
            }

            Class clazz = instance.getClass();
            Field field = clazz.getDeclaredField(property.getName());
            field.setAccessible(true);
            field.set(instance, getOneArgumentRealValue(property.getValue()));
        }
    }

    private void removeEarlyExposeBuildingBeans(String beanName) {
        earlyExposeBuildingThreadLocal.get().remove(beanName);
    }

    private Object[] getConstructorArgumentValues(BeanDefinition bd) throws Throwable {
        List<?> params = bd.getConstructorArgumentValues();
        if (params == null) {
            return null;
        }

        Object[] objects = new Object[params.size()];
        for (int i = 0; i < params.size(); i++) {
            objects[i] = getOneArgumentRealValue(params.get(i));
        }
        return objects;
    }

    private Object getOneArgumentRealValue(Object originalValue) throws Throwable {
        //获取真正参数值，主要是处理BeanReference,得到真正的Bean实例
        Object realValue = null;
        if (originalValue != null) {
            if (originalValue instanceof BeanReference) {
                BeanReference br = (BeanReference) originalValue;
                if (StringUtils.isNotBlank(br.getBeanName())) {
                    realValue = this.getBean(br.getBeanName());
                } else if (br.getType() != null) {
                    realValue = this.getBean(br.getType());
                }
            } else if (originalValue instanceof Object[]) {
                // TODO 处理集合中的bean引用
            } else if (originalValue instanceof Collection) {
                // TODO 处理集合中的bean引用
            } else if (originalValue instanceof Properties) {
                // TODO 处理properties中的bean引用
            } else if (originalValue instanceof Map) {
                // TODO 处理Map中的bean引用
            } else {
                realValue = originalValue;
            }
            //请大家完成上面未完成的逻辑
        }
        return realValue;
    }

    /**
     * 推断构造函数
     *
     * @param bd
     * @param args
     * @return
     * @throws Exception
     */
    private Constructor<?> determineConstructor(BeanDefinition bd, Object[] args) throws Exception {
        // 使用默认构造函数
        if (args == null) {
            return bd.getBeanClass().getConstructor(null);
        }

        /**
         * 如果已经构建过对象实例，那么此参数必不为空，相当于缓存作用
         * 这块逻辑针对原型模式构建对象有提升效率的作用
         * 单例，因为构建一次，就放入的容器中，作用不明显
         */
        Constructor<?> ct = bd.getConstructor();
        if (ct != null) {
            return ct;
        }

        Class[] paramClasses = new Class[args.length];
        int i = 0;
        for (Object obj : args) {
            paramClasses[i++] = obj.getClass();
        }

        // 先精确匹配
        try {
            ct = bd.getBeanClass().getConstructor(paramClasses);
        } catch (Exception e) {
            // 不处理任何异常，可以打印一些debug日志
        }

        /**
         * 如果没有匹配上，模糊匹配
         * 这里要求构造参数的数量：真实的构造函数参数的数量与手动传入的构造函数参数数量必须一致
         * 只是判断是否是子类情况
         */

        if (ct == null) {
            Constructor<?>[] constructors = bd.getBeanClass().getConstructors();
            outer:
            for (Constructor<?> ct1 : constructors) {
                Class<?>[] paramTypes = ct1.getParameterTypes();
                if (paramTypes.length == args.length) {
                    int k = 0;
                    for (Object arg : args) {
                        if (!paramTypes[k++].isAssignableFrom(arg.getClass())) {
                            continue;
                        }
                        ct = ct1;
                        break outer;
                    }
                }
            }
        }

        if (ct != null) {
            // 原型模式
            if (!bd.isPrototype()) {
                bd.setConstructor(ct);
            }
            return ct;
        }
        throw new RuntimeException("不存在对应的构造函数" + bd.getBeanClass().getName());
    }

    public void registerTypeMap() throws Throwable {
        for (String name : this.beanDefinitionMap.keySet()) {
            Class<?> type = this.getType(name);
            //映射本类
            this.registerTypeMap(name, type);
            //父类
            this.registerSuperClassTypeMap(name, type);
            //接口
            this.registerInterfaceTypeMap(name, type);
        }
    }

    private void registerTypeMap(String name, Class<?> type) {
        Set<String> names2type = this.typeMap.get(type);
        if (names2type == null) {
            names2type = new HashSet<>();
            this.typeMap.put(type, names2type);
        }
        names2type.add(name);
    }

    private void registerInterfaceTypeMap(String name, Class<?> type) {
        Class<?>[] interfaces = type.getInterfaces();
        if (interfaces.length > 0) {
            for (Class<?> interf : interfaces) {
                this.registerTypeMap(name, interf);
                //递归找父接口
                this.registerInterfaceTypeMap(name, interf);
            }
        }
    }

    private void registerSuperClassTypeMap(String name, Class<?> type) {
        Class<?> superClass = type.getSuperclass();
        if (superClass != null && !superClass.equals(Object.class)) {
            this.registerTypeMap(name, superClass);
            //递归找父类
            this.registerSuperClassTypeMap(name, superClass);
            //找父类实现的接口注册
            this.registerInterfaceTypeMap(name, superClass);
        }
    }

    // 应用bean初始化前的处理
    private Object applyPostProcessBeforeInitialization(Object bean, String beanName) throws Throwable {
        for (BeanPostProcessor bpp : this.beanPostProcessors) {
            bean = bpp.postProcessBeforeInitialization(bean, beanName);
        }
        return bean;
    }

    // 应用bean初始化后的处理
    private Object applyPostProcessAfterInitialization(Object bean, String beanName) throws Throwable {
        for (BeanPostProcessor bpp : this.beanPostProcessors) {
            bean = bpp.postProcessAfterInitialization(bean, beanName);
        }
        return bean;
    }
}
